package main

import (
	"crypto/rand"
	"encoding/csv"
	"fmt"
	"io"
	"log"
	"os"
	"time"

	pbc "github.com/Nik-U/pbc"
)

// PerformanceResult (已更新)
type PerformanceResult struct {
	Experiment     string
	Parameter      string
	Value          int
	ProofGenTimeMs float64
	VerifyTimeMs   float64
	// TagGenTimeMs        float64
	// AvgProofGenTimeMs   float64
	// TotalProofGenTimeMs float64
	// AvgVerifyTimeMs     float64
	// TotalVerifyTimeMs   float64
}

func basicExp(params *SystemParams) {
	log.Println("--- Starting Basic Functionality Experiment ---")

	log.Println("--- Step 1: Actors Initialization ---")
	dataOwner := NewDataOwner(params)
	csp := NewCSP(params)
	verifier := NewBlockchainVerifier(params)

	log.Println("--- Step 2: Data Outsourcing ---")
	const fileSize = 1 * 1024 * 1024 // 1MB
	const blockSize = 2 * 1024       // 2KB

	numBlocks := fileSize / blockSize
	challengedBlocks := numBlocks

	dummyFile := make([]byte, fileSize)
	rand.Read(dummyFile)

	blocks, tags, metadata, err := dataOwner.TagGen(dummyFile, "testfile.txt", blockSize)
	if err != nil {
		log.Fatalf("TagGen failed: %v", err)
	}
	csp.StoreFile(metadata, blocks, tags)
	log.Printf("DO generated %d tags for a file with %d blocks. CSP stored the file.", len(tags), metadata.NumBlocks)

	log.Println("\n--- Step 3: Normal Auditing Process ---")
	tau := verifier.GetCurrentState()
	log.Printf("Blockchain state (tau): %s", tau)

	proof, err := csp.ProofGen(metadata, dataOwner.PK, tau, challengedBlocks)
	if err != nil {
		log.Fatalf("ProofGen failed: %v", err)
	}

	isValid := verifier.VerifyProof(proof, dataOwner.PK, metadata)
	if isValid {
		log.Println("✅SUCCESS: Proof is valid in the normal case.")
	} else {
		log.Println("❌FAILURE: Proof is invalid in the normal case.")
	}

	log.Println("\n--- Step 4: Auditing After Data Tampering (Improved Test Logic) ---")
	tauTampered := verifier.GetCurrentState()
	log.Printf("New blockchain state for tampering check (tau): %s", tauTampered)

	// 1. Determine which block to tamper with *based on the challenge*
	// We'll pick the block challenged by the first index (i=0)
	tamperedBlockIndex := params.F(tauTampered, 0, metadata.NumBlocks)
	log.Printf("!!! Tampering data on CSP, block index: %d (deterministically chosen from challenge set)!!!", tamperedBlockIndex)

	// 2. Tamper the chosen block
	tamperedValue := params.Pairing.NewZr().Set1()
	originalBlock := params.Pairing.NewZr().Set(csp.storedData[metadata.Name][tamperedBlockIndex])
	csp.storedData[metadata.Name][tamperedBlockIndex].Add(csp.storedData[metadata.Name][tamperedBlockIndex], tamperedValue)

	// 3. Generate proof (it's now guaranteed to be invalid)
	// We challenge all blocks to make the detection certain
	tamperedProof, _ := csp.ProofGen(metadata, dataOwner.PK, tauTampered, metadata.NumBlocks)

	// 4. Verify
	isTamperedValid := verifier.VerifyProof(tamperedProof, dataOwner.PK, metadata)
	if !isTamperedValid {
		log.Println("✅SUCCESS: Data tampering was successfully detected.")
	} else {
		log.Println("❌FAILURE: Data tampering was NOT detected.")
	}

	// 5. Restore data for subsequent tests
	csp.storedData[metadata.Name][tamperedBlockIndex].Set(originalBlock)
	log.Println("Data on CSP restored to original state.")
}

// 实验1：考察文件大小的影响
func fileSizeExp(writer *csv.Writer, params *SystemParams) {
	log.Println("\n\n<<<<<<<<<< NIPPDP Experiment 1: Performance vs. File Size >>>>>>>>>>")
	const NumBlocks = 4096
	const K = 5
	fileSizeOptionsMB := []int{4, 8, 12, 16, 20}

	dataOwner := NewDataOwner(params)
	csp := NewCSP(params)
	verifier := NewBlockchainVerifier(params)

	for _, sizeMB := range fileSizeOptionsMB {
		fileSize := sizeMB * 1024 * 1024
		blockSize := fileSize / NumBlocks
		log.Printf("\n--- Testing with FileSize: %dMB ---", sizeMB)

		dummyFile := make([]byte, fileSize)
		rand.Read(dummyFile)

		blocks, tags, metadata, _ := dataOwner.TagGen(dummyFile, "exp1_file", blockSize)

		csp.StoreFile(metadata, blocks, tags)

		challengedBlocks := NumBlocks
		tau := verifier.GetCurrentState()

		// ✅ 执行一次
		startProofGen := time.Now()
		proof, _ := csp.ProofGen(metadata, dataOwner.PK, tau, challengedBlocks)
		proofGenTime := time.Since(startProofGen)

		startVerify := time.Now()
		// TODO:验证执行k次-> 模拟blockChain验证每一个云
		// ✅ 执行k次
		for range K {
			verifier.VerifyProof(proof, dataOwner.PK, metadata)
		}
		verifyTime := time.Since(startVerify)

		result := PerformanceResult{
			Experiment: "FileSize", Parameter: "FileSize(MB)", Value: sizeMB,
			ProofGenTimeMs: float64(proofGenTime.Microseconds()) / 1000.0,
			VerifyTimeMs:   float64(verifyTime.Microseconds()) / 1000.0,
			// TagGenTimeMs:        float64(tagGenTime.Microseconds()) / 1000.0,
			// AvgProofGenTimeMs:   float64(proofGenTime.Microseconds()) / 1000.0,
			// TotalProofGenTimeMs: float64(proofGenTime.Microseconds()) / 1000.0,
			// AvgVerifyTimeMs:     float64(verifyTime.Microseconds()) / 1000.0,
			// TotalVerifyTimeMs:   float64(verifyTime.Microseconds()) / 1000.0,
		}
		log.Printf("[PERF] Result: %+v", result)
		writer.Write([]string{
			result.Experiment, result.Parameter, fmt.Sprintf("%d", result.Value),
			fmt.Sprintf("%.3f", result.ProofGenTimeMs),
			fmt.Sprintf("%.3f", result.VerifyTimeMs),
			// fmt.Sprintf("%.3f", result.TagGenTimeMs),
			// fmt.Sprintf("%.3f", result.AvgProofGenTimeMs),
			// fmt.Sprintf("%.3f", result.TotalProofGenTimeMs),
			// fmt.Sprintf("%.3f", result.AvgVerifyTimeMs),
			// fmt.Sprintf("%.3f", result.TotalVerifyTimeMs),
		})
		writer.Flush()
	}
}

// 实验2：考察 CSP 数量的影响 (模拟多云独立审计)
func cspCountExp(writer *csv.Writer, params *SystemParams) {
	log.Println("\n\n<<<<<<<<<< NIPPDP Experiment 2: Performance vs. Number of CSPs (Multi-Cloud Simulation) >>>>>>>>>>")
	const NumBlocks = 4096
	const BlockSize = 1024
	const FileSize = NumBlocks * BlockSize
	cspCounts := []int{5, 10, 15, 20, 25, 30}

	dataOwner := NewDataOwner(params)
	verifier := NewBlockchainVerifier(params)
	dummyFile := make([]byte, FileSize)
	rand.Read(dummyFile)

	for _, k := range cspCounts {
		log.Printf("\n--- Testing with %d CSPs ---", k)

		var blocks []*pbc.Element
		var tags []*pbc.Element
		var metadata *FileMetadata
		for i := 0; i < k; i++ {
			blocks, tags, metadata, _ = dataOwner.TagGen(dummyFile, fmt.Sprintf("exp2_file_csp%d", i), BlockSize)
		}

		csps := make([]*CloudServiceProvider, k)
		for i := range k {
			csps[i] = NewCSP(params)
			csps[i].StoreFile(metadata, blocks, tags)
		}

		var proofGenTime, verifyTime time.Duration
		for _, csp := range csps {
			tau := verifier.GetCurrentState()

			startProof := time.Now()
			proof, _ := csp.ProofGen(metadata, dataOwner.PK, tau, NumBlocks)
			proofGenTime += time.Since(startProof)

			startVerify := time.Now()
			verifier.VerifyProof(proof, dataOwner.PK, metadata)
			verifyTime += time.Since(startVerify)
		}

		// 证明一次，验证k次
		proofGenTime = proofGenTime / time.Duration(k)

		result := PerformanceResult{
			Experiment: "CSPCount", Parameter: "NumCSPs", Value: k,
			ProofGenTimeMs: float64(proofGenTime.Microseconds()) / 1000.0,
			VerifyTimeMs:   float64(verifyTime.Microseconds()) / 1000.0,
			// TagGenTimeMs:        float64(totalTagGenTime.Microseconds()) / 1000.0,
			// AvgProofGenTimeMs:   float64(avgProofGenTime.Microseconds()) / 1000.0,
			// TotalProofGenTimeMs: float64(totalProofGenTime.Microseconds()) / 1000.0,
			// AvgVerifyTimeMs:     float64(avgVerifyTime.Microseconds()) / 1000.0,
			// TotalVerifyTimeMs:   float64(totalVerifyTime.Microseconds()) / 1000.0,
		}
		log.Printf("[PERF] Result (for %d CSPs): %+v", k, result)
		writer.Write([]string{
			result.Experiment, result.Parameter, fmt.Sprintf("%d", result.Value),
			fmt.Sprintf("%.3f", result.ProofGenTimeMs),
			fmt.Sprintf("%.3f", result.VerifyTimeMs),
			// fmt.Sprintf("%.3f", result.TagGenTimeMs),
			// fmt.Sprintf("%.3f", result.AvgProofGenTimeMs),
			// fmt.Sprintf("%.3f", result.TotalProofGenTimeMs),
			// fmt.Sprintf("%.3f", result.AvgVerifyTimeMs),
			// fmt.Sprintf("%.3f", result.TotalVerifyTimeMs),
		})
		writer.Flush()
	}
}

// 实验3：考察文件分块数量的影响
func numBlocksExp(writer *csv.Writer, params *SystemParams) {
	log.Println("\n\n<<<<<<<<<< NIPPDP Experiment 3: Performance vs. Number of Blocks >>>>>>>>>>")
	const FileSize = 4 * 1024 * 1024
	const K = 5
	numBlocksOptions := []int{1024, 2048, 4096, 8192, 16384}

	dataOwner := NewDataOwner(params)
	csp := NewCSP(params)
	verifier := NewBlockchainVerifier(params)

	for _, numBlocks := range numBlocksOptions {
		blockSize := FileSize / numBlocks
		log.Printf("\n--- Testing with %d blocks ---", numBlocks)

		dummyFile := make([]byte, FileSize)
		rand.Read(dummyFile)

		blocks, tags, metadata, _ := dataOwner.TagGen(dummyFile, "exp3_file", blockSize)

		csp.StoreFile(metadata, blocks, tags)

		challengedBlocks := numBlocks
		tau := verifier.GetCurrentState()

		startProofGen := time.Now()
		proof, _ := csp.ProofGen(metadata, dataOwner.PK, tau, challengedBlocks)
		proofGenTime := time.Since(startProofGen)

		startVerify := time.Now()
		for range K {
			verifier.VerifyProof(proof, dataOwner.PK, metadata)
		}
		verifyTime := time.Since(startVerify)

		result := PerformanceResult{
			Experiment: "NumBlocks", Parameter: "NumberOfBlocks", Value: numBlocks,
			ProofGenTimeMs: float64(proofGenTime.Microseconds()) / 1000.0,
			VerifyTimeMs:   float64(verifyTime.Microseconds()) / 1000.0,
			// TagGenTimeMs:        float64(tagGenTime.Microseconds()) / 1000.0,
			// AvgProofGenTimeMs:   float64(proofGenTime.Microseconds()) / 1000.0,
			// TotalProofGenTimeMs: float64(proofGenTime.Microseconds()) / 1000.0,
			// AvgVerifyTimeMs:     float64(verifyTime.Microseconds()) / 1000.0,
			// TotalVerifyTimeMs:   float64(verifyTime.Microseconds()) / 1000.0,
		}
		log.Printf("[PERF] Result: %+v", result)
		writer.Write([]string{
			result.Experiment, result.Parameter, fmt.Sprintf("%d", result.Value),
			fmt.Sprintf("%.3f", result.ProofGenTimeMs),
			fmt.Sprintf("%.3f", result.VerifyTimeMs),
		})
		writer.Flush()
	}
}

func main() {
	logFile, err := os.OpenFile("experiment_nippdp.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
	if err != nil {
		log.Fatalf("Failed to open log file: %v", err)
	}
	defer logFile.Close()
	mw := io.MultiWriter(os.Stdout, logFile)
	log.SetOutput(mw)

	csvFile, err := os.Create("results_nippdp.csv")
	if err != nil {
		log.Fatalf("Failed to create CSV file: %v", err)
	}
	defer csvFile.Close()
	csvWriter := csv.NewWriter(csvFile)
	defer csvWriter.Flush()
	csvWriter.Write([]string{"Experiment", "Parameter", "Value", "", "ProofGenTime(ms)", "VerifyTime(ms)"})

	log.Println("--- Global System Setup ---")
	params, err := NewSystemParams()
	if err != nil {
		log.Fatalf("Failed to create system params: %v", err)
	}

	// basicExp(params)
	fileSizeExp(csvWriter, params)
	cspCountExp(csvWriter, params)
	numBlocksExp(csvWriter, params)

	log.Println("\n\n<<<<<<<<<< All experiments for NIPPDP finished. >>>>>>>>>>")
}
